﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.ModelBinding;
using System.Web.Http.OData;
using System.Web.Http.OData.Query;
using System.Web.Http.OData.Routing;
using Microsoft.Data.OData;
using System.IO;
using System.ComponentModel.DataAnnotations;
using System.Web.Http.OData.Query.Validators;
using System.Web;
using System.Text;
using System.IO.Compression;
using System.Web.Http.OData.Builder;
using Microsoft.Data.OData.Query.SemanticAst;

namespace CloudStorageLight.Core.Web
{
    public abstract class BlobODataV3ControllerBase : ODataController
    {

        public static System.Web.Http.OData.Builder.ODataConventionModelBuilder GetModelBuilder(string name)
        {
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
            builder.EntitySet<FileItemV3>(name);
            return builder;

        }

        protected abstract BlobService CreateBlobService();

        protected virtual BlobUser GetCurrentUser()
        {
            return new BlobUser(System.Web.HttpContext.Current.User);
        }

        private static ODataValidationSettings _validationSettings = new ODataValidationSettings();

        private IHttpActionResult ResponseErrorMessage(Exception ex)
        {
            var blobEx = ex as BlobException;
            if (blobEx != null)
            {
                if (blobEx.Code == BlobExceptionCode.NotFoundFile)
                {
                    return NotFound();
                }
                HttpError err = new HttpError(ex.Message);
                return ResponseMessage(Request.CreateErrorResponse(HttpStatusCode.BadRequest, err));
            }
            return ResponseMessage(Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.Message));
        }

        public class MyOrderByValidator : FilterQueryValidator
        {
            public List<string> FreeKeywords { get; set; }
            public List<string> EqDirectories { get; set; }

            public List<string> StartWithDirectories { get; set; }

            public MyOrderByValidator()
            {
                EqDirectories = new List<string>();
                StartWithDirectories = new List<string>();
                FreeKeywords = new List<string>();

            }

            public override void ValidateSingleValueFunctionCallNode(Microsoft.Data.OData.Query.SingleValueFunctionCallNode node, ODataValidationSettings settings)
            {
                if (node.Name == "substringof" && node.Arguments.Count() == 2)
                {
                    var col = node.Arguments.Skip(1).First() as SingleValuePropertyAccessNode;
                    var val = node.Arguments.FirstOrDefault() as ConstantNode;
                    if (val != null)
                    {
                        var literal = BlobUtil.UnescapeDataString(val.LiteralText.Trim('\''));
                        if (col.Property.Name == "SearchContent" && !string.IsNullOrWhiteSpace(literal)) FreeKeywords.Add(literal);
                    }
                }
                if (node.Name == "startswith" && node.Arguments.Count() == 2)
                {
                    var col = node.Arguments.FirstOrDefault() as SingleValuePropertyAccessNode;
                    var val = node.Arguments.Skip(1).First() as ConstantNode;
                    if (val != null)
                    {
                        var literal = BlobUtil.UnescapeDataString(val.LiteralText.Trim('\''));
                        if (col.Property.Name == "Directory" && !string.IsNullOrWhiteSpace(literal)) StartWithDirectories.Add(literal);
                    }
                }

                base.ValidateSingleValueFunctionCallNode(node, settings);
            }


            public override void ValidateBinaryOperatorNode(Microsoft.Data.OData.Query.SemanticAst.BinaryOperatorNode binaryOperatorNode, ODataValidationSettings settings)
            {
                var left = binaryOperatorNode.Left as SingleValuePropertyAccessNode;
                var right = binaryOperatorNode.Right as ConstantNode;

                if (left != null && right != null)
                {
                    if (left.Property.Name == "Directory")
                    {
                        EqDirectories.Add(BlobUtil.UnescapeDataString(right.LiteralText.Trim('\'')));
                    }
                }
                base.ValidateBinaryOperatorNode(binaryOperatorNode, settings);
            }


        }

        // GET: odata/Data
        public async Task<IHttpActionResult> GetData(ODataQueryOptions<FileItemV3> queryOptions)
        {

            queryOptions = new ODataQueryOptions<FileItemV3>(queryOptions.Context, this.Request);
            // validate the query.
            try
            {
                int maxsize = 0;
                var validateor = new MyOrderByValidator();
                if (queryOptions.Filter != null)
                {
                    validateor.Validate(queryOptions.Filter, new ODataValidationSettings() { });

                }
                else
                {
                    validateor.EqDirectories.Add("/");
                    queryOptions.Validate(_validationSettings);
                }

                var fulltextSerach = System.Web.HttpContext.Current.Request.QueryString["$search"];
                if (fulltextSerach != null) fulltextSerach = fulltextSerach.Trim('\'');

                int skip;
                if (queryOptions.Skip != null && int.TryParse(queryOptions.Skip.RawValue, out skip))
                {
                    maxsize = skip;
                }
                int top;
                if (queryOptions.Top != null && int.TryParse(queryOptions.Top.RawValue, out top))
                {
                    maxsize += top;
                }

                using (var svc = CreateBlobService())
                {
                    var blobItems = new List<BlobItem>();

                    if (string.IsNullOrWhiteSpace(fulltextSerach) && validateor.FreeKeywords.Count == 0)
                    {
                        foreach (var dire in validateor.EqDirectories)
                        {
                            blobItems.AddRange(await svc.ListFilesAsync(GetCurrentUser(), dire, maxsize + 1, false));

                        }
                        foreach (var dire in validateor.StartWithDirectories)
                        {
                            blobItems.AddRange(await svc.ListFilesAsync(GetCurrentUser(), dire, maxsize + 1, true));

                        }
                    }
                    else
                    {
                        foreach (var dire in validateor.EqDirectories.Concat(validateor.StartWithDirectories))
                        {
                            if (!string.IsNullOrWhiteSpace(fulltextSerach))
                            {
                                blobItems.AddRange(await svc.SearchAsync(GetCurrentUser(), fulltextSerach, dire, maxsize));
                            }
                            foreach (var keyword in validateor.FreeKeywords)
                            {
                                blobItems.AddRange(await svc.SearchAsync(GetCurrentUser(), keyword, dire, maxsize));
                            }
                        }
                        blobItems = blobItems.Distinct().ToList();
                    }

                    var items = blobItems.Select(x => new FileItemV3(x));
                    IQueryable results = queryOptions.ApplyTo(items.AsQueryable());
                    return Ok(results as IQueryable<FileItemV3>);

                }
            }
            catch (ODataException ex)
            {
                return BadRequest(ex.Message);
            }
            catch (Exception ex)
            {
                return ResponseErrorMessage(ex);
            }
        }

        // GET: odata/Data(5)
        public async Task<IHttpActionResult> Get([FromODataUri] string key, ODataQueryOptions<FileItemV3> queryOptions)
        {
            // validate the query.
            try
            {
                queryOptions.Validate(_validationSettings);
                using (var svc = CreateBlobService())
                {
                    var file = await svc.GetFileAsync(GetCurrentUser(), FileItemV3.ConvertIdToPath(key));
                    var item = new FileItemV3(file);
                    return Ok(item);
                }
            }
            catch (ODataException ex)
            {
                return BadRequest(ex.Message);
            }
            catch(Exception ex)
            {
                return ResponseErrorMessage(ex);
            }
        }


        private void LogError(string action, Exception ex)
        {
            using (var svc = CreateBlobService())
            {
                svc.EntryLog(GetCurrentUser(), action, this.Request.RequestUri.ToString(), ex.ToString());
            }
        }

        private bool CheckFileItem(FileItem fileItem)
        {
            if (fileItem.Directory != BlobItem.GetDirectoryPath(FileItem.ConvertIdToPath(fileItem.Id))) return false;
            return true;
        }

        // PUT: odata/Data(5)
        public async Task<IHttpActionResult> Put([FromODataUri] string key, FileItem fileItem)
        {
            if (!ModelState.IsValid || !CheckFileItem(fileItem))
            {
                return BadRequest(ModelState);
            }

            if (key != fileItem.Id)
            {
                return BadRequest();
            }

            using (var svc = CreateBlobService())
            {
                try
                {
                    var item = await svc.UpdateFileAsync(GetCurrentUser(), fileItem.Owner, FileItem.ConvertIdToPath(fileItem.Id), fileItem.Title);
                    return Updated(item);
                }
                catch (Exception ex)
                {
                    LogError("Put", ex);
                    return ResponseErrorMessage(ex);
                }
            }
        }

        // POST: odata/Data
        public async Task<IHttpActionResult> Post(FileItem fileItem)
        {
            if (!ModelState.IsValid || !CheckFileItem(fileItem))
            {
                return BadRequest(ModelState);
            }

            using (var svc = CreateBlobService())
            {
                try
                {
                    BlobItem item;
                    if (fileItem.Path.EndsWith("/"))
                    {
                        item = await svc.CreateDirectoryAsync(GetCurrentUser(), FileItem.ConvertIdToPath(fileItem.Id), fileItem.Owner);
                    }
                    else
                    {
                        item = await svc.CreateFileAsync(GetCurrentUser(), fileItem.Owner, FileItem.ConvertIdToPath(fileItem.Id), fileItem.Title);
                    }
                    return Created(item);
                }
                catch (Exception ex)
                {
                    LogError("Post", ex);
                    return ResponseErrorMessage(ex);
                }

            }
        }

        // PATCH: odata/Data(5)
        [AcceptVerbs("PATCH", "MERGE")]
        public async Task<IHttpActionResult> Patch([FromODataUri] string key, Delta<FileItem> delta)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            using (var svc = CreateBlobService())
            {
                try
                {
                    var path = FileItem.ConvertIdToPath(key);
                    var file = await svc.GetFileAsync(GetCurrentUser(), path);
                    var fileItem = new FileItem(file);
                    delta.Patch(fileItem);
                    var item = await svc.UpdateFileAsync(GetCurrentUser(), fileItem.Owner, path, fileItem.Title);
                    return Updated(item);
                }
                catch (Exception ex)
                {
                    LogError("Patch", ex);
                    return ResponseErrorMessage(ex);
                }
            }
        }

        // DELETE: odata/Data(5)
        public async Task<IHttpActionResult> Delete([FromODataUri] string key, ODataActionParameters parameters)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            using (var svc = CreateBlobService())
            {
                try
                {
                    var path = FileItem.ConvertIdToPath(key);
                    if (path.EndsWith("/"))
                    {
                        await svc.DeleteDirectoryAsync(GetCurrentUser(), path);
                    }
                    else
                    {
                        await svc.DeleteFileAsync(GetCurrentUser(), path);
                    }
                    return StatusCode(HttpStatusCode.NoContent);
                }
                catch (Exception ex)
                {
                    LogError("Delete", ex);
                    return ResponseErrorMessage(ex);
                }
            }
        }
    }
}
